package cc.rome753.wat;

import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera1Enumerator;
import org.webrtc.EglBase;
import org.webrtc.FileVideoCapturer;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.MediaStreamTrack;
import org.webrtc.MyVideoSink;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RtpReceiver;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;

import java.util.ArrayList;
import java.util.List;

public class PeerClientFragment extends Fragment implements SignalingServer.Callback {
    private static final String TAG = PeerClientFragment.class.getSimpleName();

    //region normal
    private String flag;
    //endregion

    //region global variable
    boolean host;
    boolean front;
    EglBase.Context eglBaseContext;
    PeerConnectionFactory peerConnectionFactory;
    SignalingServer signalingServer;
    MyVideoSink myVideoSink = new MyVideoSink();
    //endregion

    //region local variable
    SurfaceTextureHelper surfaceTextureHelper;
    VideoCapturer videoCapturer;
    VideoSource videoSource;
    VideoTrack videoTrack;
    VideoTrack remoteVideoTrack;
    AudioSource audioSource;
    AudioTrack audioTrack;
    PeerConnection peerConnection;
    SurfaceViewRenderer localView;
    SurfaceViewRenderer remoteView;
    MediaStream mediaStream;
    MediaStream mediaStreamRemote;
    IceCandidate iceCandidate;
    //endregion

    public PeerClientFragment(boolean front) {
        this.front = front;
        if (front) {
            flag = "local";
        } else {
            flag = "remote";
        }
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        Log.i(TAG, "onCreate()");
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.i(TAG, "onCreateView()");
        return inflater.inflate(R.layout.fragment_main, null);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Log.i(TAG, "onViewCreated()");
        super.onViewCreated(view, savedInstanceState);
        init();

        if (!PermissionUtil.isMediaPermissionsGranted(getContext())) {
            PermissionUtil.gotoSettings(getContext(), true);
        }
    }

    @Override
    public void onDestroyView() {
        Log.i(TAG, "onDestroyView()");
        super.onDestroyView();
        releaseAll();
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy()");
        super.onDestroy();
    }

    @Override
    public void onPeerJoin() {
        Log.i(TAG, "onPeerJoin()");
        // remote peer join
        host = true;
        step1();
    }

    @Override
    public void onPeerLeave(String msg) {
        Log.i(TAG, "onPeerLeave(), msg" + msg);
        step11();
    }

    @Override
    public void onOfferReceived(String sdp) {
        Log.i(TAG, "onOfferReceived(), sdp" + sdp);
        host = false;
        step4(sdp);
        step5();
    }

    @Override
    public void onAnswerReceived(String sdp) {
        Log.i(TAG, "onAnswerReceived(), sdp" + sdp);
        step8(sdp);
//        step9();
    }

    @Override
    public void onIceCandidateReceived(IceCandidate candidate) {
        Log.i(TAG, "onIceCandidateReceived(), candidate" + candidate);
        peerConnection.addIceCandidate(new IceCandidate(
                candidate.sdpMid,
                candidate.sdpMLineIndex,
                candidate.sdp));
    }

    private void init() {
        Log.i(TAG, "init()");
        eglBaseContext = ((MainActivity) getActivity()).getEglBaseContext();
        peerConnectionFactory = ((MainActivity) getActivity()).getPeerConnectionFactory();

        surfaceTextureHelper = SurfaceTextureHelper.create(flag + "CaptureThread", eglBaseContext);
        // create VideoCapturer
        videoCapturer = createCameraCapturer(front);
        videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
        videoCapturer.initialize(surfaceTextureHelper, getActivity().getApplicationContext(), videoSource.getCapturerObserver());

        localView = getView().findViewById(R.id.localView);
        localView.setMirror(front);
        localView.init(eglBaseContext, null);

        remoteView = getView().findViewById(R.id.remoteView);
        remoteView.init(eglBaseContext, null);

        // create VideoTrack
        videoTrack = peerConnectionFactory.createVideoTrack(front ? "100" : "102", videoSource);
//        // display in localView
        videoTrack.addSink(localView);

        audioSource = peerConnectionFactory.createAudioSource(new MediaConstraints());
        audioTrack = peerConnectionFactory.createAudioTrack(front ? "101" : "103", audioSource);

        mediaStream = peerConnectionFactory.createLocalMediaStream(flag + "MediaStream");
        mediaStream.addTrack(videoTrack);
//        mediaStream.addTrack(audioTrack);

//        call(mediaStreamLocal, mediaStreamRemote);
        signalingServer = SignalingServer.get();

        List<PeerConnection.IceServer> iceServers = new ArrayList<>();

        peerConnection = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter(flag + "Connection") {
            @Override
            public void onIceCandidate(IceCandidate candidate) {
                Log.i(TAG, "onIceCandidate(), " + candidate);
                super.onIceCandidate(candidate);
                iceCandidate = candidate;
                if (host) {
                    step9(candidate);
                } else {
                    step10(candidate);
                }
            }

            @Override
            public void onAddStream(MediaStream mediaStream) {
                Log.i(TAG, "onAddStream(), " + mediaStream);
                Log.i(TAG, "audioTracks size: " + mediaStream.audioTracks.size());
                Log.i(TAG, "videoTracks size: " + mediaStream.videoTracks.size());
                Log.i(TAG, "preservedVideoTracks size: " + mediaStream.preservedVideoTracks.size());
                super.onAddStream(mediaStream);
                mediaStreamRemote = mediaStream;
                remoteVideoTrack = mediaStreamRemote.videoTracks.get(0);
                getActivity().runOnUiThread(() -> {
                    remoteVideoTrack.addSink(remoteView);
                    remoteVideoTrack.addSink(myVideoSink);
                });
            }

            @Override
            public void onRemoveStream(MediaStream mediaStream) {
                Log.i(TAG, "onRemoveStream " + mediaStream);
                remoteVideoTrack = mediaStream.videoTracks.get(0);
                getActivity().runOnUiThread(() -> {
                    remoteVideoTrack.removeSink(myVideoSink);
                    remoteVideoTrack.removeSink(remoteView);
                    remoteView.clearImage();
                });
                mediaStreamRemote = null;
            }

            @Override
            public void onSignalingChange(PeerConnection.SignalingState signalingState) {
                switch (signalingState) {
                    case HAVE_LOCAL_OFFER:
                        Log.i(TAG, "HAVE_LOCAL_OFFER");
                        break;
                    case HAVE_REMOTE_OFFER:
                        Log.i(TAG, "HAVE_REMOTE_OFFER");
                        break;

                    case HAVE_LOCAL_PRANSWER:
                        Log.i(TAG, "HAVE_LOCAL_PRANSWER");
                        break;
                    case HAVE_REMOTE_PRANSWER:
                        Log.i(TAG, "HAVE_REMOTE_PRANSWER");
                        break;
                }
            }

            @Override
            public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
                Log.i(TAG, "onAddTrack " + mediaStreams);
                rtpReceiver.SetObserver(receiverObserver);
            }
        });

        peerConnection.addStream(mediaStream);

        //step 0: join room
        step0();
    }

    private RtpReceiver.Observer receiverObserver = new RtpReceiver.Observer() {
        @Override
        public void onFirstPacketReceived(MediaStreamTrack.MediaType mediaType) {
            Log.i(TAG, "onFirstPacketReceived(), mediaType: " + mediaType);
        }
    };


    private void releaseAll() {
        if (signalingServer != null) {
            signalingServer.leave(this, "destroy");
        }

        if (peerConnection != null) {
            peerConnection.dispose();
            peerConnection = null;
        }

//        if (mediaStream != null) {
//            mediaStream.dispose();
            mediaStream = null;
//        }

        if (videoCapturer != null) {
            videoCapturer.dispose();
            videoCapturer = null;
        }

        if (videoSource != null) {
            videoSource.dispose();
            videoSource = null;
        }

//        if (videoTrack != null) {
//            videoTrack.dispose();
            videoTrack = null;
//        }

        if (localView != null) {
            localView.release();
            localView = null;
        }

        if (remoteView != null) {
            remoteView.release();
            remoteView = null;
        }

        if (surfaceTextureHelper != null) {
            surfaceTextureHelper.dispose();
            surfaceTextureHelper = null;
        }

    }

    private VideoCapturer createCameraCapturer(boolean isFront) {
        Camera1Enumerator enumerator = new Camera1Enumerator(false);
        final String[] deviceNames = enumerator.getDeviceNames();

        // First, try to find front facing camera
        for (String deviceName : deviceNames) {
            if (isFront ? enumerator.isFrontFacing(deviceName) : enumerator.isBackFacing(deviceName)) {
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);

                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }

        return null;
    }


    private void step0() {
        Log.i(TAG, "step 0");
        signalingServer.join(this);
    }

    private void step1() {
        Log.i(TAG, "step 1");
        videoCapturer.startCapture(480, 640, 30);

        SessionDescription sessionDescription = peerConnection.getLocalDescription();
        if (sessionDescription == null) {
            peerConnection.createOffer(new SdpAdapter(flag + " offer sdp") {
                @Override
                public void onCreateSuccess(SessionDescription sessionDescription) {
                    super.onCreateSuccess(sessionDescription);
                    step2(sessionDescription);
                    step3(sessionDescription);
                }
            }, new MediaConstraints());
        } else {
            step2(sessionDescription);
            step3(sessionDescription);
        }

    }

    private void step2(SessionDescription sessionDescription) {
        Log.i(TAG, "step 2");
        peerConnection.setLocalDescription(new SdpAdapter(flag + " set local"), sessionDescription);
    }

    private void step3(SessionDescription sessionDescription) {
        Log.i(TAG, "step 3");
        signalingServer.sendSdp(this, sessionDescription.description);
    }

    private void step4(String sdp) {
        Log.i(TAG, "step 4");
        videoCapturer.startCapture(480, 640, 30);
        peerConnection.setRemoteDescription(new SdpAdapter(flag + "SetRemote"),
                new SessionDescription(SessionDescription.Type.OFFER, sdp));
    }

    private void step5() {
        Log.i(TAG, "step 5");
        peerConnection.createAnswer(new SdpAdapter(flag + "AnswerSdp") {
            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                super.onCreateSuccess(sessionDescription);
                step6(sessionDescription);
                step7(sessionDescription);
            }
        }, new MediaConstraints());
    }

    private void step6(SessionDescription sessionDescription) {
        Log.i(TAG, "step 6");
        peerConnection.setLocalDescription(new SdpAdapter(flag + "SetLocalSdp"), sessionDescription);
    }

    private void step7(SessionDescription sessionDescription) {
        Log.i(TAG, "step 7");
        signalingServer.sendSdp(this, sessionDescription.description);
    }

    private void step8(String sdp) {
        Log.i(TAG, "step 8");
        peerConnection.setRemoteDescription(new SdpAdapter(flag + "SetRemoteSdp"),
                new SessionDescription(SessionDescription.Type.ANSWER, sdp));
    }

    private void step9(IceCandidate candidate) {
        Log.i(TAG, "step 9");
        if(iceCandidate == null)
            return;

        signalingServer.sendIceCandidate(this, iceCandidate);
    }

    private void step10(IceCandidate candidate) {
        Log.i(TAG, "step 10");
        if(iceCandidate == null)
            return;
        signalingServer.sendIceCandidate(this, iceCandidate);
    }

    private void step11() {
        try {
//            remoteVideoTrack.removeSink(remoteView);
            videoCapturer.stopCapture();
            if (mediaStreamRemote != null) {
                peerConnection.removeStream(mediaStreamRemote);
            }
        } catch (InterruptedException e) {
            Log.e(TAG, "", e);
        }
    }
}
